////////////////////////////////////////////////////////////////////////////////
//
// labFisInterf.cc - acquire data for SNS Lab Fis Interference experiment
// 
// 1st release: A. Lusiani <a.lusiani@sns.it> April 2003
//
// this program includes parts from poll.c, Part of Comedilib,
// Copyright (c) 1999,2000 David A. Schleef <ds@schleef.org>
// poll.c may be freely modified, distributed, and combined with
// other software, as long as proper attribution is given in the
// source code.
//

////////////////////////////////////////////////////////////////////////////////
//
// Modifications:
//
//

////////////////////////////////////////////////////////////////////////////////
//
// headers
//

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <comedilib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <sys/select.h>
}

#ifndef __CINT__
#include "TFile.h"
#include "TApplication.h"
#include "TCanvas.h"
#include "TH1.h"
#include "TH2.h"
#include "TProfile.h"
#include "TNtuple.h"
#include "TRandom.h"
#endif

#include "labFisDaq.h"

////////////////////////////////////////////////////////////////////////////////
//
// function declarations
//

////////////////////////////////////////////////////////////////////////////////
//
// variable  declarations
//

const char* 		COMEDI_DEV = "/dev/comedi0";
int			AI_SUBDEV;
int			AO_SUBDEV;
//--- comedi sub-devices for Digital Output
int			DO_SUBDEV;

//--- buffer size for streaming data out of the driver
const unsigned int	COMEDI_BUFSIZ =	(4*1024*1024);

//--- Analog Input definitions: ADC #0 is the microphone
const unsigned int 	MIC_AI_CHAN=0;
const unsigned int 	TEMP1_AI_CHAN=1;
const unsigned int 	TEMP2_AI_CHAN=2;

//--- definitions to describe input channels for asynchronous DAQ
#define AISCAN_BEG	(0)
#define AISCAN_NUM	(1)
unsigned int aiScanList[AISCAN_NUM];

//--- Analog Input voltage range
const float 		AI_VMIN = -1;
const float 		AI_VMAX = 1;

//--- select analog in voltage reference from GROUND
const unsigned int	AI_AREF = AREF_GROUND;

//--- rate at which the input is sampled in Hz
unsigned int 		AISCAN_RATE = 200000;
#define 		AISCAN_SAMPLES (4000)

//--- Analog Out definition: DAC #0 driver Amplitude Modulation
const unsigned int 	AM_AO_CHAN = 0;

//--- AM value to turn sound generator ON
double 			AM_OSC_ON = -4.2;
//--- AM value to turn sound generator OFF
double			AM_OSC_OFF = 0;

//--- select Analog Output voltage reference from GROUND
const unsigned int	AO_AREF = AREF_GROUND;

//--- Digital Output definition: DO #0 is trigger driver
const unsigned int 	TRIG_DO_CHAN=0;

////////////////////////////////////////////////////////////////////////////////
//
// code
//

int main(int argc, char *argv[])
{
  int i;
  int rc;
  comedi_t *cifh;
  comedi_cmd cmd;
  lsampl_t maxDataAi;
  lsampl_t maxDataAo;
  double minVoltsAi, maxVoltsAi;
  double minVoltsAo, maxVoltsAo;
  unsigned int rangetAi;
  comedi_range* rangepAi;
  unsigned int rangetAo;
  comedi_range* rangepAo;
  //--- buffer for ni_pcimio Analog Input driver (16-bit unsigned samples)
  sampl_t buf[AISCAN_SAMPLES*AISCAN_NUM];
  int pendingNewline = 0;
  unsigned int len;
  unsigned int scansTot;
  
  //--- endpoints of acquisition are valid numbers
  rc = comedi_set_global_oor_behavior(COMEDI_OOR_NUMBER);

  //--- open Comedi device to access DAQ card
  cifh = comedi_open(COMEDI_DEV);
  if(!cifh){
    perror(COMEDI_DEV);
    exit(1);
  }

  //--- set comedi device streaming buffer size
  rc = comedi_set_buffer_size(cifh, AI_SUBDEV, COMEDI_BUFSIZ);
  if (rc<0) {
    fprintf(stderr, "comedi_set_buffer_size: %s\n",strerror(errno));
    exit(1);
  } else {
    rc = comedi_get_buffer_size(cifh, AI_SUBDEV);
    if (rc<0) {
      perror("comedi_get_buffer_size");
    } else {
      fprintf(stderr, "comedi buffer size for AI set to 0x%x bytes\n", rc);
    }
  }

  //--- find which subdevice offers Analog Input
  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_AI, 0);
  if (rc<0){
    comedi_perror("comedi_find_device AI");
    exit(1);
  }
  AI_SUBDEV = rc;

  //--- find which subdevice offers Analog Output
  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_AO, 0);
  if (rc<0){
    comedi_perror("comedi_find_device AO");
    exit(1);
  }
  AO_SUBDEV = rc;

  //--- find which subdevice offers Digital Output
  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_DO,0);
  if (rc<0){
    rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_DIO, 0);
    if (rc<0){
      comedi_perror("comedi_find_device DO and DIO");
      exit(1);
    }
    DO_SUBDEV = rc;
    rc = comedi_dio_config(cifh, rc, 0, COMEDI_OUTPUT);
    if (rc<0){
      comedi_perror("comedi_dio_config");
      exit(1);
    }
  } else {
    DO_SUBDEV = rc;
  }
  fprintf(stderr, "Using Digital Output subdevice %d\n", DO_SUBDEV);

  //--- find AI range for -10 Volt .. 10 Volt
  rc = comedi_find_range(cifh, AI_SUBDEV,
			 MIC_AI_CHAN, UNIT_volt, AI_VMIN, AI_VMAX);
  if (rc<0){
    comedi_perror("comedi_find_range");
    exit(1);
  }
  rangetAi = rc;

  //--- get ptr to range struct
  rangepAi = comedi_get_range(cifh, AI_SUBDEV, MIC_AI_CHAN, rangetAi);
  //--- get maximum possible sample for AI
  maxDataAi = comedi_get_maxdata(cifh, AI_SUBDEV, MIC_AI_CHAN);
  minVoltsAi = comedi_to_phys(0, rangepAi, maxDataAi);
  maxVoltsAi = comedi_to_phys(maxDataAi, rangepAi, maxDataAi);

  fprintf(stderr, "Comedi offers AI range n. %d, %f .. %f\n",
	 rangetAi, minVoltsAi, maxVoltsAi);

  //--- build channel list for DAQ scan of microphone
  for(i=0;i<AISCAN_NUM;i++){
    aiScanList[i] = CR_PACK(AISCAN_BEG+i, rangetAi, AI_AREF);
  }

  //--- find AO range for -10 Volt .. 10 Volt
  rc = comedi_find_range(cifh, AO_SUBDEV,
			 AM_AO_CHAN, UNIT_volt,
			 AM_OSC_ON, AM_OSC_OFF);
  if (rc<0){
    comedi_perror("comedi_find_range");
    exit(1);
  }
  rangetAo = rc;

  //--- get ptr to range struct
  rangepAo = comedi_get_range(cifh, AO_SUBDEV, AM_AO_CHAN, rangetAo);
  //--- get maximum possible sample for AO
  maxDataAo = comedi_get_maxdata(cifh, AO_SUBDEV, AM_AO_CHAN);
  minVoltsAo = comedi_to_phys(0, rangepAo, maxDataAo);
  maxVoltsAo = comedi_to_phys(maxDataAo, rangepAo, maxDataAo);

  fprintf(stderr, "Comedi offers AO range n. %d, %f .. %f\n",
	 rangetAo, minVoltsAo, maxVoltsAo);

  //--- get DAC values for voltages that drive sound oscillator
  lsampl_t soundOnData = comedi_from_phys(AM_OSC_ON, rangepAo, maxDataAo);
  lsampl_t soundOffData = comedi_from_phys(AM_OSC_OFF, rangepAo, maxDataAo);


  //////////////////////////////////////////////////////////////////////////////
  //
  // initialize Root
  //
  // code inspired from:
  // - test/hsimple.cxx of Root 3.05.3 package
  //

  // Create a new ROOT binary machine independent file.
  // This file is now becoming the current directory.
  TFile hfile("labFisInterf.root", "RECREATE", "Lab Fis: Interferenza");

  //--- allocate Root Tree
  TTree *tree = new TTree("nt", "Lab Fisica interference ntuple");

  //--- Root ntuple definition: see Root/tutorials/cernbuild.C

  //--- allocate an unsigned integer variable
  UInt_t micSample;
  //--- add to the "tree" ntuple a branch "mic" with a sinle leaf "mic"
  tree->Branch("mic",&micSample,"mic/I");

  TH1F *hsMicro = new TH1F("hsMicro", "Audio signal distribution",
			   100, -5, 5);
  hsMicro->SetXTitle("Volts");
  hsMicro->SetYTitle("events");

  TProfile *hsMicroTimeProf =
    new TProfile("hsMicroTimeProf", "Audio signal vs. time",
		 AISCAN_SAMPLES, -0.5/AISCAN_RATE,
		 (static_cast<double>(AISCAN_SAMPLES)-0.5)/AISCAN_RATE,
		 minVoltsAi, maxVoltsAi);
  hsMicroTimeProf->SetXTitle("seconds");
  hsMicroTimeProf->SetYTitle("Volts");

  //--- ask for non-blocking I/O on comedi
  fcntl(comedi_fileno(cifh),F_SETFL,O_NONBLOCK);

  //--- allocate object to setup terminal input
  labFisTermIoHandler tioh(stdin);
  //--- setup terminal for immediate keypress input with no echo
  tioh.SetImmedNoecho();

  //////////////////////////////////////////////////////////////////////////////
  //
  //	begin use of DAQ card
  //

  //--- set trigger Digital Out to zero
  fprintf(stderr, "Set trigger line to zero\n");
  rc = comedi_dio_write(cifh, DO_SUBDEV, TRIG_DO_CHAN, 0);
  if (rc<0){
    comedi_perror("comedi_dio_write");
    exit(1);
  }

  //--- Turn oscillator off
  fprintf(stderr, "Turn oscillator off\n");
  rc = comedi_data_write(cifh, AO_SUBDEV, AM_AO_CHAN,
			 rangetAo, AO_AREF, soundOffData);
  if (rc<0){
    comedi_perror("comedi_write_data");
    exit(1);
  }

  //--- wait 0.5 seconds for echoes to fade off
  usleep(500000);

  //
  // read temperatures
  //
  for(int ii = 0; i<20; i++) {
  fprintf(stderr,"Temperatures:");
  for(unsigned int iChan=TEMP1_AI_CHAN; iChan<=TEMP2_AI_CHAN; iChan++) {
    lsampl_t data;
    
    rc = comedi_data_read(cifh, AI_SUBDEV, iChan, rangetAi, AI_AREF, &data);
    if (rc<0){
      comedi_perror("comedi_read_data");
      exit(1);
    }
    fprintf(stderr," %8.4f", comedi_to_phys(data, rangepAi, maxDataAi));
  }
  fprintf(stderr,"\n");
  usleep(200000);
  }

  //--- prepare AI scan for microphone's input
  //--- the scan is started by external trigger driver via a Digital Output
  labFisScanPrepare(cifh, AI_SUBDEV, &cmd,
		    TRIG_EXT, AISCAN_RATE, TRIG_COUNT, AISCAN_SAMPLES,
		    aiScanList, AISCAN_NUM);
  
  //--- start comedi Analog Input scan
  rc = comedi_command(cifh, &cmd);
  fprintf(stderr,"Start DAQ, rc= %d\n", rc);
  if (rc<0) {
    fprintf(stderr,"errno=%d\n",errno);
    comedi_perror("comedi_command");
    exit(1);
  }

  //
  // submit instructions to Comedi in order to:
  // - pulse digital out #0 to trigger DAQ 
  // - start oscillator
  //
  {
    comedi_insn insn[16];
    comedi_insnlist il;
    lsampl_t delay;
    unsigned int ilCou = 0;
  
    //--- clear instruction array
    memset(insn, 0, sizeof(insn));
	
    //--- setup pointer to instruction array
    il.insns = insn;

    //--- delays in ns
    delay = 1000;

    //--- set trigger line to 1
    lsampl_t trigOn[2];
    //--- set bits corresponding to Digital Out channels to be changed
    trigOn[0] = 1 << TRIG_DO_CHAN;
    //--- set bits corresponding to Digital Out channels to be turned on/off
    trigOn[1] = 1 << TRIG_DO_CHAN;
    insn[ilCou].insn = INSN_BITS;
    insn[ilCou].n = 2;
    insn[ilCou].data = &trigOn[0];
    insn[ilCou].subdev = DO_SUBDEV;
    ilCou++;

    //---- wait
    insn[ilCou].insn = INSN_WAIT;
    insn[ilCou].n = 1;
    insn[ilCou].data = &delay;
    ilCou++;

    //--- set trigger line to 0
    lsampl_t trigOff[2];
    //--- set bits corresponding to Digital Out channels to be changed
    trigOff[0] = 1;
    //--- set bits corresponding to Digital Out channels to be turned on/off
    trigOff[1] = 0;
    insn[ilCou].insn = INSN_BITS;
    insn[ilCou].n = 2;
    insn[ilCou].data = &trigOff[0];
    insn[ilCou].subdev = DO_SUBDEV;
    ilCou++;

    //---- wait (acquire some silence data)
    insn[ilCou].insn = INSN_WAIT;
    insn[ilCou].n = 1;
    insn[ilCou].data = &delay;
    ilCou++;

    //---- enable oscillator
    insn[ilCou].insn = INSN_WRITE;
    insn[ilCou].n = 1;
    insn[ilCou].data = &soundOnData;
    insn[ilCou].subdev = AO_SUBDEV;
    insn[ilCou].chanspec = CR_PACK(AM_AO_CHAN, rangetAo, AO_AREF);
    ilCou++;

    //--- set number of instructions
    il.n_insns = ilCou;

    //--- execute instructions
    rc = comedi_do_insnlist(cifh, &il);
    if (rc < 0){
      comedi_perror("comedi_do_insnlist");
      exit(1);
    }
  }

  //--- init sample number
  scansTot = 0;

  //--- DAQ loop (endless)
  while (1) {
    //--- read available data
    rc = labFisScanRead(cifh, AI_SUBDEV, &buf[0], sizeof(buf),
			static_cast<long int>(1e6), 10);

    //--- end endless loop if end of data reached
    if (rc == 0) {
      if (pendingNewline) fprintf(stderr, "\n");
      fprintf(stderr, "EOF, ending scan after reading %6d samples\n",
	      scansTot);
      break;
    }

    //--- read available data
    if (rc > 0) {
      unsigned int i;

      len = rc/sizeof(sampl_t);
      for(i=0; i < len; i++, scansTot++) {

	//--- fill histogram of read voltages
	hsMicro->Fill(comedi_to_phys(buf[i], rangepAi, maxDataAi));
	//--- fill histogram of read voltages vs. time in seconds
	hsMicroTimeProf->Fill(static_cast<double>(scansTot)/AISCAN_RATE,
			      comedi_to_phys(buf[i], rangepAi, maxDataAi));

	//---initialize Root ntuple variable(s) for this event
	micSample = buf[i];
	//--- store this event ntuple
	tree->Fill();

	fprintf(stderr, "%4x ", buf[i]);
	pendingNewline = 1;
	if (i%16==15) {
	  fprintf(stderr,"\n");
	  pendingNewline = 0;
	}
      }
      continue;
    }

    if (-rc == LFDAQ_STDINRDY) {
      unsigned char ch = fgetc(stdin);
      if (ch == 25) {
	fprintf(stderr,"  --- <control-Y> received, quitting ... --- \n");
	break;
      } else {
	fprintf(stderr,"  --- Press <control-Y> to quit --- \n");
      }
      continue;
    }

    if (-rc == LFDAQ_TIMEOUT) {
      fprintf(stderr,"Timeout waiting for data \n");
      break;
    }

    break;
  }

  //--- Save all objects in this file
  hfile.Write();

  //--- Close the file.
  hfile.Close();

  //--- open saved file for reading
  TFile hfileR("labFisInterf.root", "READ");

  //--- recover a pointer to the histogram "hsMicroTimeProf"
  TH1D *hs = (TH1D *)hfileR.Get("hsMicroTimeProf");
  hs->SetMaximum(0.3*AI_VMAX);
  hs->SetMinimum(0.3*AI_VMIN);

  //--- init an object to get Root GUI on a canvas
  TApplication theApp("App", &argc, argv);

  //--- init an object to get a graphical canvas
  TCanvas *canvas = new TCanvas("canvas", "Microphone vs. time", 600, 300);

  //--- draw the histogram
  hs->Draw("hist C");

  //--- get the canvas updated
  canvas->Update();

  //--- recover pointer to Root tree (ntuple) "nt"
  TTree *treeR = (TTree *)hfileR.Get("nt");

  //--- start root GUI: "Exit ROOT" will return to the next statement
  theApp.Run(kTRUE);

  //--- Close the file.
  hfile.Close();

  return 0;
}
